/* Viper Split# Compiler
 * Copyright (c) 2013 Split# Developers
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 */


/* NOTE: The above license DOES NOT APPLY to the Viper and GruntXProductions
 * namespace. Viper is released under the GNU General Public License v3
 */

using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using GruntXProductions;
using Viper;
using Viper.Opcodes;

namespace SplitSharp
{
        public partial class CodeGen
        {
                private List<CodeElement> Ast;
                private List<Variable> vars = new List<Variable> ();
                private Stack<Scope> scope = new Stack<Scope> ();
                public Assembly Assembler;
                private string methodName = "";
                private int stringIndex = 0;
                private int tmpIndex = 0;
                private int ifIndex = 0;
                private string methodType = "";
                public List<string> Errors = new List<string> ();
                public List<string> Warnings = new List<string> ();

                public void Compile (List<CodeElement> ast)
                {
                        Ast = ast;
                        Assembler.Emit (new bsr ("main"));
                        Assembler.Emit (new ret ());
                        foreach (CodeElement cl in Ast) {
                                if (cl is Function) {
                                        Function func = cl as Function;
                                        methodName = func.Name;
                                        methodType = func.Type;
                                        if (func.External) {
                                                Assembler.CreateExternalSymbol (methodName);
                                                continue;
                                        }
                                        scope.Push (new Scope ());
                                        func.Arguments.Reverse ();
                                        Assembler.CreateLabel (func.Name);
                                        foreach (Declaration decl in func.Arguments) {
                                                if (decl.Type == "int") {
                                                        declareDword (decl.Name);
                                                        Assembler.Emit (new push_ptr (getVarName (decl.Name)));
                                                        Assembler.Emit (new dstore ());
                                                } else if (decl.Type == "string") {
                                                        declareCharPtr (decl.Name);
                                                        Assembler.Emit (new push_ptr (getVarName (decl.Name)));
                                                        Assembler.Emit (new dstore ());
                                                }
                                        }
                                        func.Arguments.Reverse ();
                                        CompileBlock (func.body);
                                        if (func.Type != "void") {
                                                bool returns = false;
                                                foreach (CodeElement ce in func.body.Body) {
                                                        if (ce is Return) {
                                                                returns = true;
                                                        }
                                                }
                                                if (!returns)
                                                        Errors.Add ("Function '" + func.Name + "' does not return type " + func.Type);
                                        }
                                        scope.Pop ();
                                        Assembler.Emit (new ret ());
                                }
                        }
                }

                public void Call (FunctionCall func)
                {
                        if (!isFunction (func.Name))
                                Errors.Add ("The name " + func.Name + " does not exist in the current context!");
                        int argc = 0;
                        if (func.Arguments.Count != getFunction (func.Name).Arguments.Count) {
                                Errors.Add (func.Name + " does not take " + func.Arguments.Count.ToString () + " arguments!");
                                return;
                        }
                        foreach (Expression exp in func.Arguments) {
                                if (getFunction (func.Name).Arguments [argc].Type == "string")
                                        ParseStringExpression (exp.tokens);
                                else if (getFunction (func.Name).Arguments [argc].Type == "int" || getFunction (func.Name).Arguments [argc].Type == "ptr")
                                        ParseArthemeticExpression (exp.tokens);
                                argc++;
                        }
                        Assembler.Emit (new bsr (func.Name));
                }

                public void CompileBlock (CodeBlock block)
                {
                        foreach (CodeElement ce in block.Body) {
                                CompileLine (ce);
                        }
                }

                public void ParseArthemeticExpression (List<Token> ints)
                {
                        Instruction op = null;
                        for (int i = 0; i < ints.Count; i++) {

                                if (ints [i] is Tokens.IntLiteral) {
                                        Tokens.IntLiteral il = ints [i] as Tokens.IntLiteral;
                                        Assembler.Emit (new push_d ((uint)il.Value));
                                } else if (ints [i] is FunctionCall) {
                                        FunctionCall fc = ints [i] as FunctionCall;
                                        Call (fc);

                                } else if ( ints[i] is PointerDereference) {
                                        PointerDereference pd = ints[i] as PointerDereference;
                                        ParseArthemeticExpression(new List<Token>(new Token[]{pd.Pointer}));
                                        Assembler.Emit(new dload());
                                }  else if (ints [i] is ArrayRetrieve) {
                                        ArrayRetrieve ar = ints [i] as ArrayRetrieve;
                                        ParseArthemeticExpression (ar.IndexExpression);
                                        Assembler.Emit (new push_ptr (getVarName (ar.Variable)));
                                        Assembler.Emit (new ldelem_d ());
                                } else if (ints[i] is Expression) {
                                        Expression exp = ints[i] as Expression;
                                        ParseArthemeticExpression(exp.tokens);
                                } else if (ints [i] is Tokens.StringLiteral) {
                                        Tokens.StringLiteral sl = ints [i] as Tokens.StringLiteral;
                                        Assembler.Emit (new push_ptr (declareStringLiteral (sl.Value)));
                                } else if (ints [i] is AddressOf) {
                                        AddressOf ao = ints [i] as AddressOf;
                                        Assembler.Emit (new push_ptr (getVarName (ao.Symbol)));
                                } else if (isVariable (ints [i].ToString ())) {
                                        if (getVarType (ints [i].ToString ()) != VType.Dword)
                                                Errors.Add ("Type '" + ints [i].ToString () + "' can not be converated to type Int");
                                        Assembler.Emit (new push_ptr (getVarName (ints [i].ToString ())));
                                        Assembler.Emit (new dload ());

                                }
                                if (op == null) {
                                        if (ints [i] is Tokens.Add) {
                                                op = new Add ();
                                        } else if (ints [i] is Tokens.Sub) {
                                                op = new sub ();
                                        } else if (ints [i] is Tokens.Div) {
                                                op = new div ();
                                        } else if (ints [i] is Tokens.Mul) {
                                                op = new mul ();
                                        }
                                } else {
                                        Assembler.Emit (op);
                                        op = null;
                                }
                        }
                }

                public void ParseStringExpression (List<Token> str)
                {
                        bool Add = false;
                        int strings = 0;
                        for (int i = 0; i < str.Count; i++) {
                                if (str [i] is Tokens.StringLiteral) {
                                        Tokens.StringLiteral sl = str [i] as Tokens.StringLiteral;
                                        Assembler.Emit (new push_ptr (declareStringLiteral (sl.Value)));
                                } else if (str [i] is FunctionCall) {
                                        FunctionCall fc = str [i] as FunctionCall;
                                        Call (fc);
                                } else if (str [i] is ArrayRetrieve) {
                                        ArrayRetrieve ar = str [i] as ArrayRetrieve;
                                        ParseArthemeticExpression (ar.IndexExpression);
                                        Assembler.Emit (new push_ptr (getVarName (ar.Variable)));
                                        Assembler.Emit (new ldelem_d ());
                                } else if (isVariable (str [i].ToString ())) {
                                        VType type = getVarType (str [i].ToString ());
                                        if (type == VType.String) {
                                                Assembler.Emit (new push_ptr (getVarName (str [i].ToString ())));
                                                Assembler.Emit (new dload ());
                                        }
                                } else if (System.Diagnostics.Process.GetCurrentProcess ().StartInfo.EnvironmentVariables.ContainsKey (str [i].ToString ())) {
                                        Assembler.Emit (new push_ptr (declareStringLiteral (System.Diagnostics.Process.GetCurrentProcess ().StartInfo.EnvironmentVariables [str [i].ToString ()])));
                                } else {
                                        if (!Add) {
                                                if (str [i] is Tokens.Add) {
                                                        if (strings > 0) {
                                                                Assembler.Emit (new sysf (3));
                                                                strings++;
                                                        } else

                                                                Add = true;
                                                } else {
                                                        Errors.Add ("Unexpected symbol '" + str [i].ToString () + "'");
                                                }
                                        } else {
                                                Assembler.Emit (new sysf (3));
                                                Add = false;
                                                strings++;
                                        }
                                }
                        }
                        if (Add || strings > 0) {
                                Assembler.Emit (new sysf (3));
                        }
                }

                public void CompileLine (CodeElement element)
                {
                        if (element is Declaration) {
                                Declaration decl = element as Declaration;
                                if (isVariable (decl.Name)) {
                                        Errors.Add (decl.Type + " " + decl.Name + " has already been declared in this scope!");
                                } else if (decl.Type == "int") {
                                        declareDword (decl.Name);
                                        if (decl.Value != null) {
                                                ParseArthemeticExpression (decl.Value.Value);
                                                Assembler.Emit (new push_ptr (getVarName (decl.Name)));
                                                Assembler.Emit (new dstore ());
                                        }
                                } else if (decl.Type == "string") {
                                        declareCharPtr (decl.Name);
                                        if (decl.Value != null) {
                                                ParseStringExpression (decl.Value.Value);
                                                Assembler.Emit (new push_ptr (getVarName (decl.Name)));
                                                Assembler.Emit (new dstore ());
                                        }
                                } else if (decl.Type == "ptr") {
                                        declareDword (decl.Name);
                                        if (decl.Value != null) {
                                                ParseArthemeticExpression (decl.Value.Value);
                                                Assembler.Emit (new push_ptr (getVarName (decl.Name)));
                                                Assembler.Emit (new dstore ());
                                        }
                                } else {

                                        Errors.Add ("Unknown type '" + decl.Type + "'");
                                }

                        } else if (element is Assign) {
                                Assign asn = element as Assign;

                                if (isVariable (asn.Variable)) {
                                        if (getVarType (asn.Variable) == VType.Dword) {
                                                ParseArthemeticExpression (asn.Value);
                                                Assembler.Emit (new push_ptr (getVarName (asn.Variable)));
                                                Assembler.Emit (new dstore ());
                                        } else if (getVarType (asn.Variable) == VType.String) {
                                                ParseStringExpression (asn.Value);
                                                Assembler.Emit (new push_ptr (getVarName (asn.Variable)));
                                                Assembler.Emit (new dstore ());
                                        } else
                                                Errors.Add ("Unknown type for " + asn.Variable);
                                } else {
                                        Errors.Add ("The name " + asn.Variable + " does not exist in the current context!");

                                }

                        } else if (element is ArrayAssign) {
                                ArrayAssign asn = element as ArrayAssign;
                                if (getVarType (asn.Variable) == VType.DArray)
                                        ParseArthemeticExpression (asn.Value);
                                else if (getVarType (asn.Variable) == VType.StringArray)
                                        ParseStringExpression (asn.Value);
                                ParseArthemeticExpression (asn.Index);
                                Assembler.Emit (new push_ptr (getVarName (asn.Variable)));
                                Assembler.Emit (new stelem_d ());
                        } else if (element is inlineAsm) {
                                string ASM = ((inlineAsm)element).Asm;
                                foreach (Scope s in scope) {
                                        foreach (Variable v in s.Variables) {
                                                ASM = ASM.Replace ("$" + v.Name, v.RealName);
                                        }
                                }
                                vasm.MainClass.Complile (ASM + "\n", ref Assembler);
                        } else if (element is TryBody) {
                                compileTry ((TryBody)element);
                        } else if (element is IfStatement) {
                                IfStatement ifstat = element as IfStatement;
                                compileIf (ifstat);
                        } else if (element is FixedArray) {
                                FixedArray array = element as FixedArray;
                                if (array.Type == "int")
                                        declareDwordArray (array.Name, (uint)(getSize (array.Type) * array.Size));
                                else if (array.Type == "string") {
                                        declareStringArray (array.Name, (uint)(4 * array.Size));
                                }
                        } else if (element is ForStatement) {
                                ForStatement whilestat = element as ForStatement;
                                compileFor (whilestat);
                        } else if (element is MetaElement) {
                                Assembler.Metadata.Add (((MetaElement)element).Tag, ((MetaElement)element).Value);
                        } else if (element is WhileStatement) {
                                WhileStatement whilestat = element as WhileStatement;
                                compileWhile (whilestat);
                        } else if (element is Instance) {
                                Instance newInst = element as Instance;
                                Call (newInst.Ctor);

                        } else if (element is Return) {
                                Return ret = element as Return;
                                if (ret.Value.Count != 0 && methodType == "void") {
                                        Errors.Add ("Can not return value for void!");
                                } else {
                                        if (methodType == "int" || methodName == "ptr")
                                                ParseArthemeticExpression (ret.Value);
                                        else if (methodType == "string")
                                                ParseStringExpression (ret.Value);
                                        Assembler.Emit (new ret ());
                                }
                        } else if (element is FunctionCall) {
                                FunctionCall func = element as FunctionCall;
                                Call (func);
                        }

                }
        }
}

